﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace wf8_2fSinTask
{
    public partial class Form1 : Form
    {

        Bitmap bitmap1, bitmap2;
        int W, H;        //same for bitmap1,bitmap2
        double om1, om2; //radians/s  
        Brush brush 
            = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, 15, 15),
                Color.Red, Color.Blue, 45);
        Pen pen;
        PointF[] ptsfSin1, ptsfSin2;
        double phi01, phi02; //radians
        double DPhi = 0.02;   //radians

        CancellationTokenSource _cts1, _cts2;
        delegate void MyDlgt();
        event EventHandler<PointsEventArgs> MyEvPtsfSin1;
        event EventHandler<PointsEventArgs> MyEvPtsfSin2;
        object lockObject = new object(); //for lock
        int N2; //number of rounds in task t2, after click 'Stop Task2'

        class PointsEventArgs : EventArgs        //Using events,as you can access UI 
        {
            public PointF[] Points { get; set; } //controls only from the thread on 
            public PointsEventArgs(PointF[] ptsf)  //which it was created.
            {
                this.Points = ptsf;              //Hence: Invoke methods will be used 
            }                    //only in handlers, in main thread.
        } 

        public Form1()
        {
            InitializeComponent();
            W = piB1.Width;
            H = piB1.Height;
            pen = new Pen(brush, 29);
            bitmap1 = new Bitmap(W, H);
            bitmap2 = new Bitmap(W, H);
            piB1.Image = bitmap1;
            piB2.Image = bitmap2;
            om1 = 3 * 2 * Math.PI / W; //3== three periods of sin
            om2 = 4 * 2 * Math.PI / W;
            ptsfSin1 = new PointF[W + 1];
            ptsfSin2 = new PointF[W + 1];
            MyEvPtsfSin1 += Form1_myEvPtsfSin1; 
            MyEvPtsfSin2 += new EventHandler<PointsEventArgs>(Form1_myEvPtsfSin2); 
        }

        /* TASK 1 */

        void Form1_myEvPtsfSin1(object sender, PointsEventArgs e) 
        {
            lock (lockObject)
            {
                using (var g = Graphics.FromImage(bitmap1))
                {
                    g.Clear(piB1.BackColor);
                    g.DrawLines(pen, e.Points);
                    piB1.Invoke(new MyDlgt(piB1.Refresh));
                }
                TxBN1.Invoke(new MyDlgt(() =>
                {
                    int n = int.Parse(TxBN1.Text); //n==after last click 'Wipe1'
                    TxBN1.Text = (++n).ToString();
                }));
            }
        }

        private void BStartT1_MouseDown(object sender, MouseEventArgs e)
        {
            TxBN1.Text = "0";  //Not empty, as int.Parse in above handler would fail
            _cts1 = new CancellationTokenSource();  //A new token is required to Start
            Task t1 = new Task(() => LoopRaisingMyEvs1(_cts1.Token, ptsfSin1), _cts1.Token);
            t1.Start();
            BStartT1.Enabled = false;
        }

        private void BStopT1_MouseDown(object sender, MouseEventArgs e)
        {
            _cts1.Cancel();
            BStartT1.Enabled = true;
        }

        public void LoopRaisingMyEvs1(CancellationToken ct, PointF[] pts)
        {
            for (int i = 0; i < 5000; i++)
            {
                if (ct.IsCancellationRequested) break;
                phi01 += DPhi;
                phi01 %= 2 * Math.PI;
                for (int x = 0; x <= W; x++)
                {
                    int y = H / 2 - (int)(H / 2 * Math.Sin(om1 * x - phi01));
                    pts[x] = new PointF(x, y);
                }
                OnMyEvPtsfSin1();
            }//for.
        }

        void OnMyEvPtsfSin1()
        {
            if (MyEvPtsfSin1 != null) MyEvPtsfSin1(this, new PointsEventArgs(ptsfSin1));
        }

        private void BWipe1_MouseDown(object sender, MouseEventArgs e)
        {
            phi01 = 0;
            using (Graphics g = Graphics.FromImage(bitmap1)) g.Clear(Color.White);
            piB1.Refresh();
            TxBN1.Text = "0"; //Click 'Wipe1' starts display-counter anew from 0. 
        }

        /* TASK 2 */

        void Form1_myEvPtsfSin2(object sender, PointsEventArgs e) 
        {
            lock (lockObject)
            {
                using (Graphics g = Graphics.FromImage(bitmap2))
                {
                    g.Clear(piB2.BackColor);
                    g.DrawLines(pen, e.Points);
                    piB2.Invoke(new MyDlgt(piB2.Refresh));
                }
                if (NUD2.InvokeRequired)  //check if NUD2 created on other thread 
                {
                    this.Invoke(new MyDlgt(() => //this==Form1 obj, created in Program.cs
                    {
                        decimal n = NUD2.Value;  //n==after last click 'Wipe1'
                        NUD2.Value = ++n;
                    }));
                }
                else  //else: never entered in our application
                {
                    decimal n = NUD2.Value;
                    NUD2.Value = ++n;
                }
            }
        }

        private void BStartT2_MouseDown(object sender, MouseEventArgs e)
        {
            NUD2.Value = 0;
            _cts2 = new CancellationTokenSource();
            Task t2 = new Task<int>(() => N2 = LoopRaisingMyEvs2(_cts2.Token), _cts2.Token);
            Task t3 = t2.ContinueWith(x => ContiFu3()); //x==completed Task
            t2.Start();
            BStartT2.Enabled = false;
        }

        private void BStopT2_MouseDown(object sender, MouseEventArgs e)
        {
            _cts2.Cancel();
            BStartT2.Enabled = true;
        }

        public int LoopRaisingMyEvs2(CancellationToken ct) //only 1 argument
        {
            int i = 0;
            for (; i < 5000; i++)
            {
                if (ct.IsCancellationRequested) break;
                phi02 += DPhi;
                phi02 %= 2 * Math.PI;
                for (int x = 0; x <= W; x++)
                {
                    int y = H / 2 - (int)(H / 2 * Math.Sin(om2 * x - phi02));
                    ptsfSin2[x] = new PointF(x, y); //fill directly ptsfSin2, not pts. 
                }
                OnMyEvPtsfSin2();
            }
            return i; //Real numb of turns, not effected by click on 'Wipe2': 
        }       //'i' is returned by Task t2, and assigned to N2.   

        void OnMyEvPtsfSin2()
        {
            if (MyEvPtsfSin2 != null) MyEvPtsfSin2(this, new PointsEventArgs(ptsfSin2));
        }

        private void BWipe2_MouseDown(object sender, MouseEventArgs e)
        {
            phi02 = 0;
            using (Graphics g = Graphics.FromImage(bitmap2)) g.Clear(Color.White);
            piB2.Refresh();
            NUD2.Value = 0;
        }

        void ContiFu3()
        {
            lock (lockObject)
            {
                NUD2.Invoke(new MyDlgt(() => { NUD2.Value = N2; }));
            } //N2== total numb of rounts in t2, not only after click 'Wipe2'
        }
    }
}
